﻿using System;
using System.Diagnostics.Contracts;
using System.Linq.Expressions;
using Postnuclear.Dependency.Tree;
using Postnuclear.Dependency.Visitors;

namespace Postnuclear
  {
  /// <summary>
  /// Extension methods for <see cref="Expression"/> class.
  /// </summary>
  public static class Expressions
    {
    #region Public

    /// <summary>
    /// Creates the dependency tree for the specified expression.
    /// </summary>
    /// <param name="expression">The expression which contains elements to track.</param>
    /// <returns>Value changes tracker.</returns>
    /// <remarks>
    /// Holds the listeners of provided <see cref="INotifyValueChanged"/> with strong reference.
    /// Make sure you un-subscribed from it to be garbage collected.
    /// </remarks>
    public static INotifyValueChanged Observe(this Expression expression)
      {
      Contract.Requires<ArgumentNullException>(expression != null);

      DependencyTreeBuilder builder = new DependencyTreeBuilder();
      DependencyExpressionVisitor visitor = new DependencyExpressionVisitor(builder);
      visitor.Visit(expression);

      return builder.Build();
      }

    /// <summary>
    /// Observes the specified expression and executes the specified handler when expression returning value is changed.
    /// </summary>
    /// <param name="expression">The expression which contains elements to track.</param>
    /// <param name="handler">The change handler.</param>
    /// <remarks>
    /// References the handler with weak reference, doesn't require un-subscription to allow listener to be garbage collected.
    /// </remarks>
    public static void Changed(this Expression expression, EventHandler<EventArgs> handler)
      {
      Contract.Requires<ArgumentNullException>(expression != null);
      Contract.Requires<ArgumentNullException>(handler != null);

      INotifyValueChanged observer = expression.Observe();
      observer.ValueChanged += handler.MakeWeak(eh => observer.ValueChanged -= eh);
      }

    /// <summary>
    /// Creates the dependency tree for the specified expression.
    /// </summary>
    /// <typeparam name="TValue">The type of the value.</typeparam>
    /// <param name="expression">The expression which contains elements to track.</param>
    /// <returns>Value changes tracker.</returns>
    /// <remarks>
    /// Holds the listeners of provided <see cref="IValueProvider{TValue}"/> with strong reference.
    /// Make sure you un-subscribed from it to be garbage collected.
    /// </remarks>
    public static IValueProvider<TValue> Observe<TValue>(this Expression<Func<TValue>> expression)
      {
      Contract.Requires<ArgumentNullException>(expression != null);

      Expression<Func<TValue>> replaced = expression.ReplaceMethods();

      return new ValueProvider<TValue>(replaced.Body.Observe(), replaced.Compile());
      }

    /// <summary>
    /// Observes the specified expression and executes the specified handler when expression returning value is changed.
    /// </summary>
    /// <typeparam name="TValue">The type of the value.</typeparam>
    /// <param name="expression">The expression which contains elements to track.</param>
    /// <param name="handler">The change handler.</param>
    /// <remarks>
    /// References the handler with weak reference, doesn't require un-subscription to allow listener to be garbage collected.
    /// </remarks>
    public static void Changed<TValue>(this Expression<Func<TValue>> expression, EventHandler<ValueEventArgs<TValue>> handler)
      {
      Contract.Requires<ArgumentNullException>(expression != null);
      Contract.Requires<ArgumentNullException>(handler != null);

      ValueProviderAdapter<TValue> adapter = new ValueProviderAdapter<TValue>(expression.Observe());
      adapter.ValueChanged += handler.MakeWeak(eh => adapter.ValueChanged -= eh);
      }

    /// <summary>
    /// Creates the binding between property and expression which calculates this property.
    /// </summary>
    /// <typeparam name="TMember">The type of the property.</typeparam>
    /// <typeparam name="TSource">The type of the source.</typeparam>
    /// <param name="target">The target property expression.</param>
    /// <param name="source">The source value calculation expression.</param>
    /// <param name="defaulting">
    /// If set to <c>true</c> all exceptions in source will not lead to application crash,
    /// it will lead to setting target to the default value for its type.
    /// </param>
    public static void Bind<TMember, TSource>(this Expression<Func<TMember>> target, Expression<Func<TSource>> source, bool defaulting = true)
      where TSource : TMember
      {
      Contract.Requires<ArgumentNullException>(target != null);
      Contract.Requires<ArgumentNullException>(source != null);
      Contract.Requires<ArgumentException>(target.IsSimpleMemberAccessExpression());

      new ExpressionBinding<TMember, TSource>(target, defaulting ? CreateDefaultExpression(source) : source);
      }

    /// <summary>
    /// Determines whether the specified expression is simple member access expression.
    /// </summary>
    /// <typeparam name="TMember">The type of the member.</typeparam>
    /// <param name="expression">The expression to examine.</param>
    /// <returns>
    /// <c>true</c> if the specified expression is simple member access expression; otherwise, <c>false</c>.
    /// </returns>
    [Pure]
    public static bool IsSimpleMemberAccessExpression<TMember>(this Expression<Func<TMember>> expression)
      {
      Contract.Requires<ArgumentNullException>(expression != null);

      Expression innerExpression = expression.Body;
      while (innerExpression != null && innerExpression.NodeType == ExpressionType.MemberAccess)
        {
        innerExpression = ((MemberExpression)innerExpression).Expression;
        }

      return innerExpression == null || innerExpression.NodeType == ExpressionType.Constant;
      }

    #endregion

    #region Internal

    /// <summary>
    /// Replaces all method calls with value providers.
    /// </summary>
    /// <typeparam name="TValue">The type of the value.</typeparam>
    /// <param name="expression">The expression.</param>
    /// <returns>Modified expression.</returns>
    internal static Expression<Func<TValue>> ReplaceMethods<TValue>(this Expression<Func<TValue>> expression)
      {
      Contract.Requires<ArgumentNullException>(expression != null);

      Contract.Ensures(Contract.Result<Expression<Func<TValue>>>() != null);

      return (Expression<Func<TValue>>)new ReplaceMethodCallsExpressionVisitor().Visit(expression);
      }

    /// <summary>
    /// Simplifies the specified expression by replacing values which cannot be observed with constants.
    /// </summary>
    /// <typeparam name="TExpression">The type of the expression.</typeparam>
    /// <param name="expression">The source expression.</param>
    /// <returns>Simplified expression.</returns>
    internal static TExpression Simplify<TExpression>(this TExpression expression)
      where TExpression : Expression
      {
      Contract.Requires<ArgumentNullException>(expression != null);

      Contract.Ensures(Contract.Result<TExpression>() != null);

      return (TExpression)new SimplifyExpressionVisitor().Visit(expression);
      }

    /// <summary>
    /// Weakens the references to the specified constant in the specified expression.
    /// </summary>
    /// <typeparam name="TExpression">The type of the expression.</typeparam>
    /// <param name="expression">The source expression.</param>
    /// <param name="constant">The target constant.</param>
    /// <returns>Weakened expression.</returns>
    internal static TExpression Weaken<TExpression>(this TExpression expression, object constant)
      where TExpression : Expression
      {
      Contract.Requires<ArgumentNullException>(expression != null);
      Contract.Requires<ArgumentNullException>(constant != null);

      Contract.Ensures(Contract.Result<TExpression>() != null);

      return (TExpression)new WeakenExpressionVisitor(constant).Visit(expression);
      }

    /// <summary>
    /// Creates assignment expression.
    /// </summary>
    /// <typeparam name="TMember">The type of the member.</typeparam>
    /// <typeparam name="TSource">The type of the source.</typeparam>
    /// <param name="target">The assignment target.</param>
    /// <param name="source">The assignment source.</param>
    /// <returns>Assignment expression.</returns>
    internal static Expression<Action> AssignTo<TMember, TSource>(this Expression<Func<TMember>> target, Expression<Func<TSource>> source)
      where TSource : TMember
      {
      Contract.Requires<ArgumentNullException>(target != null);
      Contract.Requires<ArgumentNullException>(source != null);
      Contract.Requires<ArgumentException>(target.IsSimpleMemberAccessExpression());

      return Expression.Lambda<Action>(Expression.Assign(target.Body, source.Body));
      }

    #endregion

    #region Private

    // Methods

    private static Expression<Func<TMember>> CreateDefaultExpression<TMember>(Expression<Func<TMember>> source)
      {
      DefaultExpression defaultExpression = Expression.Default(source.Body.Type);
      CatchBlock catchBlock = Expression.Catch(typeof(Exception), defaultExpression);
      TryExpression tryExpression = Expression.TryCatch(source.Body, catchBlock);

      return Expression.Lambda<Func<TMember>>(tryExpression);
      }

    #endregion
    }
  }